JPA 활용 1편
NOTE
JPA 엔티티 설계 시 주의점(연관관계 주인, 지연로딩, 컬렉션 초기화), 변경 감지(Dirty Checking) vs merge, DTO 활용 등 김영한 JPA 활용 강의를 정리한다.
실행 환경
버전 명시 없음 —
em.persist()/em.merge(),@Transactional등 JPA·Spring 표준 API만 사용되어 특정 버전은 확정하기 어렵다.
📌 개념
-
일대다 관계에서 연관관계의 주인은 “다” 쪽에 있다
-
무조건 사람의 말처럼 회원 또는 자동차를 중점으로 연결 짓는 것이 아닌, N 쪽에 속한 것을 더 중요하게 봐야할 수도 있음 (각각 독립적으로 보는 것이 맞음)
-
엔티티 설계시 주의점
- setter를 최대한 사용하지 않도록 하자 ( 수정 point가 어딘지 알기가 힘들다.. 유지보수 힘듬…)
-
모든 연관관계는 지연로딩으로 설정
-
지연로딩(LAZY)
- 즉시로딩 (EAGER)
- 한번 호출하면 관련된 모든 데이터를 가져오게됨 ( 연관된게 많으면 많을수록 쿼리 개수가 비약적으로 많아짐 (N + 1 문제)
- ManyToOne , OneToOne … (*ToOne)은 기본적으로 “EAGER” 설정이다 (LAZY로 변경해줘야함)
-
-
컬렉션 초기화
- 컬렉션은 필드에서 초기화 하는 것이 좋음
- hibernate가 초기화 이후에 추적을 위해 wrapping하므로 추후에 건드리는 것은 의도치 않은 결과를 나을 수 있다
- 하여, 아래처럼 초기화에만 사용하고 이후 수정은 못하게 막는게 좋음
- 컬렉션은 필드에서 초기화 하는 것이 좋음
private List<Order> orders = new ArrayList<>();모델 패턴
- 도메인 모델 패턴
- 도메인에 비즈니스 로직을 추가하고, service 계층에서 호출정도만 하는 것
- JPA를 사용시 주로 도메인 모델 패턴을 이용한다.
- 트랜잭션 스크립트 패턴
- 도메인은 도메인 역할만 하고, service 계층에 비즈니스 로직을 구현
변경 감지와 merge
-
Dutty Checking
- 엔티티 값 변경시 JPA는 dutty check를 통해 자동으로 update 쿼리를 진행한다.
- 데이터베이스의 식별자를 가지고 있는 데이터의 경우 → 준영속성 이라고 불린다
IMPORTANT
준영속 엔티티의 문제점
- JPA가 관리(영속성 컨텍스트)하고 있지 않아, 데이터를 변경해도 Dirty Checking이 일어나지 않아 자동으로 update 쿼리가 실행되지 않는다
- 해결(SOL): 변경 감지(병합 대신) 또는 merge 사용
-
변경감지
@Transactional public void updateItem(Long itemId, Book bookParam){ Item findItem = itemRepository.findOne(itemId); findItem.setPrice(bookParam.getPrice()); findItem.setName(bookParam.getName()); findItem.setStockQuantity(bookParam.getStockQuantity()); findItem.setId(bookParam.getId()); } -
병합 (merge)
- Id 있는지 check 후 있으면 merge 진행
public void save(Item item){ if (item.getId() == null){ em.persist(item); // 등록할때까지 id가 생성되지 않으므로, 생성을 해준다 }else{ em.merge(item); // 이미 등록되어있어 가져다 사용하는 느낌 } } -
병합의 문제점
- 모든 필드를 변경하기에, 필드값이 있지 않다면 null 로 업데이트를 진행할 수 있다 (왠만하면 변경감지를 이용하고, merge를 사용하지 말아야한다)
DTO
- Data Transfer Object
- 서비스 계층에서 식별자와 변경할 데이터를 명확하게 파라미터로 넘기면 된다 ( 변경할 데이터 → DTO 파라미터 이용 // DTO 서비스계층에 클래스를 만들거나 파라미터 값을 만듬)
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
/**
* 상품 수정, 권장 코드 */
@PostMapping(value = "/items/{itemId}/edit")
public String updateItem(@PathVariable Long itemId, @ModelAttribute("form")
BookForm form) {
itemService.updateItem(itemId, form.getName(), form.getPrice(),
form.getStockQuantity());
return "redirect:/items";
} }
/*
form.getName(), form.getPrice(),
form.getStockQuantity() 이걸 DTO 파라미터로 만들어도 된다. (ex Dto form)
DTO 클래스 -> 서비스 계층에 정의
*/문제) Validation 불가능
- Server Side Validation 하는 방법
- @Valid 없음 → BindingResult result 를 활용할 수 없음
- 다른 방법을 강구해봐야함
관련 문서
- (Spring) JPA - 핵심 개념 및 특징 정리 — 이 노트의 연관관계 주인/Dirty Checking 이론을 Onz 프로젝트의 실제 엔티티 설계에 적용한 실습 노트